package com.atguigu.guilimall.search.service.Impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.es.SkuEsModel;
import com.atguigu.common.utils.R;
import com.atguigu.guilimall.search.feign.ProductFeignService;
import com.atguigu.guilimall.search.vo.AttrResponseVo;
import com.google.common.collect.Lists;

import com.atguigu.common.constant.EsConstant;
import com.atguigu.guilimall.search.config.GulimallElasticSearchConfig;
import com.atguigu.guilimall.search.service.MallSearchService;
import com.atguigu.guilimall.search.vo.SearchParamVo;
import com.atguigu.guilimall.search.vo.SearchResultVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @DESCRIPTION:
 * @author: zr
 * @DATE: 2022/12/18 17:00
 */
@Service
@Slf4j
public class MallSearchServiceImpl implements MallSearchService {
    @Autowired
    RestHighLevelClient restHighLevelClient;
    @Autowired
    private ProductFeignService productFeignService;

    @Override
    public SearchResultVo search(SearchParamVo searchParamVo) {
        SearchResultVo searchResultVo = null;
        // 1、准备检索请求
        SearchRequest searchRequest = buildSearchRequest(searchParamVo);
        try {
            // 执行检索请求
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
            // 分析响应数据 封装成想要的格式
            searchResultVo = buildSearchResponse(searchResponse, searchParamVo);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return searchResultVo;
    }

    /**
     * 构建结果数据
     *
     * @param searchResponse
     * @param searchParamVo
     * @return
     */
    private SearchResultVo buildSearchResponse(SearchResponse searchResponse, SearchParamVo searchParamVo) {
        SearchResultVo searchResultVo = new SearchResultVo();
        SearchHits hits = searchResponse.getHits();
        List<SkuEsModel> skuEsModels = Lists.newArrayList();
        // 判空
        if (!CollectionUtils.isEmpty(Arrays.asList(hits.getHits()))) {
            for (SearchHit hit : hits.getHits()) {
                // 得到Resource
                String sourceSkuEs = hit.getSourceAsString();
                // 转成商品信息
                SkuEsModel skuEsModel = JSON.parseObject(sourceSkuEs, SkuEsModel.class);
                // 当keyword不为空时  需要设置高亮
                if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
                    HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                    String skuHigh = skuTitle.getFragments()[0].toString();
                    skuEsModel.setSkuTitle(skuHigh);
                }
                skuEsModels.add(skuEsModel);
            }
        }
        // 封装所查询到的所有商品
        searchResultVo.setProducts(skuEsModels);
        // 封装所有商品所涉及到的所有品牌信息
        ParsedLongTerms brandAgg = searchResponse.getAggregations().get("brand_agg");
        List<SearchResultVo.BrandVo> brandVos = Lists.newArrayList();
        for (Terms.Bucket bucket : brandAgg.getBuckets()) {
            SearchResultVo.BrandVo brandVo = new SearchResultVo.BrandVo();
            // 得到品牌Id
            long brandId = bucket.getKeyAsNumber().longValue();
            brandVo.setBrandId(Long.valueOf(brandId));
            // 得到品牌名
            String brandName = ((ParsedStringTerms) bucket.getAggregations().get("brand_name_agg")).getBuckets().get(0).getKeyAsString();
            brandVo.setBrandName(brandName);
            // 得到品牌图片
            String brandImg = ((ParsedStringTerms) bucket.getAggregations().get("brand_name_img")).getBuckets().get(0).getKeyAsString();
            brandVo.setBrandImg(brandImg);
            brandVos.add(brandVo);
        }
        searchResultVo.setBrandVos(brandVos);
        // 当前所有商品所涉及到的所有属性信息
        List<SearchResultVo.AttrVo> attrVos = Lists.newArrayList();
        ParsedNested attrAgg = searchResponse.getAggregations().get("attr_agg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attr_id_agg");
        for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
            SearchResultVo.AttrVo attrVo = new SearchResultVo.AttrVo();
            //得到属性Id
            long attrId = bucket.getKeyAsNumber().longValue();
            attrVo.setAttrId(attrId);
            // 得到属性名
            String attrName = ((ParsedStringTerms) bucket.getAggregations().get("attr_name_agg")).getBuckets().get(0).getKeyAsString();
            attrVo.setAttrName(attrName);
            // 得到属性值
            List<String> attrValueAgg = ((ParsedStringTerms) bucket.getAggregations().get("attr_value_agg")).getBuckets().stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList());
            attrVo.setAttrValue(attrValueAgg);
            attrVos.add(attrVo);
        }
        searchResultVo.setAttrVos(attrVos);
        // 当前所有商品所涉及到的所有分类信息
        ParsedLongTerms catalogAgg = searchResponse.getAggregations().get("catalog_agg");
        List<SearchResultVo.CatalogVo> catalogVos = Lists.newArrayList();
        for (Terms.Bucket bucket : catalogAgg.getBuckets()) {
            SearchResultVo.CatalogVo catalogVo = new SearchResultVo.CatalogVo();
            // 得到分类Id
            Long catalogId = bucket.getKeyAsNumber().longValue();
            catalogVo.setCatalogId(catalogId);
            // 得到分类名
            String catalogName = ((ParsedStringTerms) bucket.getAggregations().get("catalog_name_agg")).getBuckets().get(0).getKeyAsString();
            catalogVo.setCatalogName(catalogName);
            catalogVos.add(catalogVo);
        }
        searchResultVo.setCatalogVos(catalogVos);
        // 分页信息-页码
        searchResultVo.setPageNum(searchParamVo.getPageNum());
        // 分页信息-总记录数
        long total = hits.getTotalHits().value;
        searchResultVo.setTotal(total);
        // 分页信息-总页码
        Integer totalPage = Math.toIntExact(total % EsConstant.PRODUCT_PAGESIZE == 0 ? total / EsConstant.PRODUCT_PAGESIZE : (total / EsConstant.PRODUCT_PAGESIZE) + 1);
        searchResultVo.setTotalPages(totalPage);
        List<Integer> pageNavs = Lists.newArrayList();
        for (Integer i = 1; i <= totalPage; i++) {
            pageNavs.add(i);
        }
        searchResultVo.setPageNavs(pageNavs);
        // 设置面包屑导航数据 优化判空
        List<SearchResultVo.NavVo> navVoList = Optional.ofNullable(searchParamVo.getAttrs()).orElse(Lists.newArrayList()).stream().map(attr -> {
            SearchResultVo.NavVo navVo = new SearchResultVo.NavVo();
            // attrs=2_5存:6存&attrs=2_16G:8G
            String[] attrSplit = attr.split("_");
            // 远程调用根据属性Id 查找属性名
            R attrInfo = productFeignService.info(Long.valueOf(attrSplit[0]));
            if (attrInfo.getCode() == 0) {
                AttrResponseVo attrInfoData = attrInfo.getData("attr", new TypeReference<AttrResponseVo>() {
                });
                if (Objects.nonNull(attrInfoData)) {
                    navVo.setNavName(attrInfoData.getAttrName());
                }
            }
            navVo.setNavValue(attrSplit[1]);
            // 取消面包屑以后,我们要跳转到哪个地方,将请求地址的url里面的当前置空
            // 拿到所有的条件后 去掉当前
            // 设置UTF-8 编码
            String attrEncode = null;
            try {
                attrEncode = URLEncoder.encode(attr, "UTF-8");
                // 浏览器对空格编码和Java不一样
                attrEncode.replace("+", "%20");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
            String replaceLink = searchParamVo.get_queryString().replace("&attrs=" + attrEncode, "");
            navVo.setLink("http://search.gulimall.com/list.html?" + replaceLink);
            return navVo;
        }).collect(Collectors.toList());
        searchResultVo.setNavs(navVoList);
        return searchResultVo;
    }

    /**
     * ##模糊匹配，过滤(按照属性，分类，品牌，价格区间，库存)，排序，分页，高亮
     * ## 聚合分析 如果是嵌入式的属性 查询 聚合分析都应该用嵌入式
     *
     * @return
     */
    private SearchRequest buildSearchRequest(SearchParamVo searchParamVo) {
        SearchSourceBuilder builder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 模糊匹配
        if (StringUtils.hasText(searchParamVo.getKeyword())) {
            boolQuery.must(QueryBuilders.matchQuery("skuTitle", searchParamVo.getKeyword()));
        }
        // 过滤
        if (!Objects.isNull(searchParamVo.getCatalog3Id())) {
            boolQuery.filter(QueryBuilders.termQuery("catalogId", searchParamVo.getCatalog3Id()));
        }
        if (!CollectionUtils.isEmpty(searchParamVo.getBrandId())) {
            boolQuery.filter(QueryBuilders.termsQuery("brandId", searchParamVo.getBrandId()));
        }
        // 按照属性
        if (!CollectionUtils.isEmpty(searchParamVo.getAttrs())) {
            for (String paramVoAttr : searchParamVo.getAttrs()) {
                //attrs=2_5存:6存&attrs=2_16G:8G
                BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                String[] splitAttrs = paramVoAttr.split("_");
                // 属性id
                String attrId = splitAttrs[0];
                // 属性值
                String[] attrValues = splitAttrs[1].split(":");
                nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrId", attrId));
                nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));
                // 以何种方式 参与评分
                QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
                boolQuery.filter();
            }
        }
        // 是否有库存
        if (!Objects.isNull(searchParamVo.getHasStock())) {
            boolQuery.filter(QueryBuilders.termsQuery("hasStock", searchParamVo.getHasStock() == 1));
        }
        // 价格区间
        if (StringUtils.hasText(searchParamVo.getSkuPrice())) {
            // skuPrice=1_500/_500/500_ 按照格式分割
            String[] splitPrice = searchParamVo.getSkuPrice().split("_");
            RangeQueryBuilder skuPrice = QueryBuilders.rangeQuery("skuPrice");
            if (splitPrice.length == 2) {
                skuPrice.gte(splitPrice[0]).lte(splitPrice[1]);
            } else if (splitPrice.length == 1) {
                if (searchParamVo.getSkuPrice().startsWith("_")) {
                    skuPrice.lte(splitPrice[0]);
                }
                if (searchParamVo.getSkuPrice().endsWith("_")) {
                    skuPrice.gte(splitPrice[0]);
                }
            }
            boolQuery.filter(skuPrice);
        }
        builder.query(boolQuery);
        // 排序，分页，高亮
        if (StringUtils.hasText(searchParamVo.getSort())) {
            // sort=hotScore_asc/desc 热度评分
            String[] splitSort = searchParamVo.getSort().split("_");
            builder.sort(splitSort[0], splitSort[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC);
        }
        // 分页
        builder.from((searchParamVo.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE);
        builder.size(EsConstant.PRODUCT_PAGESIZE);
        // 高亮
        if (StringUtils.hasText(searchParamVo.getKeyword())) {
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            builder.highlighter(highlightBuilder);
        }
        // 聚合分析 如果是嵌入式的属性 查询 聚合分析都应该用嵌入式
        // 品牌聚合
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brand_agg").field("brandId").size(50);
        // 品牌子聚合
        brandAgg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
        brandAgg.subAggregation(AggregationBuilders.terms("brand_name_img").field("brandImg").size(1));
        builder.aggregation(brandAgg);
        // 分类聚合
        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(20);
        // 分类子聚合
        catalogAgg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
        builder.aggregation(catalogAgg);
        // 属性聚合 嵌套
        NestedAggregationBuilder nestedAgg = AggregationBuilders.nested("attr_agg", "attrs");
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId").size(50);
        // 属性名称 及属性值
        attrIdAgg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
        attrIdAgg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
        nestedAgg.subAggregation(attrIdAgg);
        builder.aggregation(nestedAgg);
        log.info("dsl查询语句{}", builder);
        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, builder);
        return searchRequest;
    }
}
